package com.lemon.cloud.admin.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.model.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lemon.cloud.admin.dto.TaskInfoDTO;
import com.lemon.cloud.admin.dto.TaskInfoParamDTO;
import com.lemon.cloud.admin.dto.TaskRecordDTO;
import com.lemon.cloud.admin.entity.SysFile;
import com.lemon.cloud.admin.entity.SysFileUploadTask;
import com.lemon.cloud.admin.mapper.SysFileUploadTaskMapper;
import com.lemon.cloud.admin.service.SysFileService;
import com.lemon.cloud.admin.service.SysFileUploadTaskService;
import com.lemon.cloud.minio.OssProperties;
import com.lemon.cloud.minio.service.OssTemplate;
import com.lemon.cloud.minio.util.FileUtils;
import com.lemon.cloud.security.util.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Service;

import java.net.URL;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class SysFileUploadTaskServiceImpl extends ServiceImpl<SysFileUploadTaskMapper, SysFileUploadTask> implements SysFileUploadTaskService {

    @Autowired
    private OssTemplate ossTemplate;

    @Autowired
    private OssProperties ossProperties;

    @Autowired
    private SysFileService sysFileService;

    @Value("${spring.application.name}")
    private String applicationName;

    public String getPath(String bucketName, String key) {
        return String.format("/" + applicationName + "/file/%s/%s", bucketName, key);
    }

    @Override
    public SysFileUploadTask getByIdentifier(String identifier) {
        return baseMapper.selectOne(new LambdaQueryWrapper<SysFileUploadTask>().eq(SysFileUploadTask::getIdentifier, identifier));
    }

    @Override
    public TaskInfoDTO getTaskInfo(String identifier) {
        SysFileUploadTask task = getByIdentifier(identifier);
        if (task == null) {
            return null;
        }

        TaskInfoDTO result = new TaskInfoDTO().setFinished(true)
                .setTaskRecord(TaskRecordDTO.convertFromEntity(task))
                .setPath(getPath(task.getBucketName(), task.getFileName()));

        boolean doesObjectExist = ossTemplate.doesObjectExist(task.getBucketName(), task.getFileName());
        if (!doesObjectExist) {
            // 未上传完，返回已上传的分片
            ListPartsRequest listPartsRequest = new ListPartsRequest(task.getBucketName(), task.getFileName(), task.getUploadId());
            PartListing partListing = ossTemplate.listParts(listPartsRequest);
            result.setFinished(false).getTaskRecord().setExitPartList(partListing.getParts());
        }
        return result;
    }

    @Override
    public TaskInfoDTO createTask(TaskInfoParamDTO param) {
        String bucketName = ossProperties.getBucketName();
        String original = param.getOriginal();

        String key = FileUtils.generateFileName(original);

        String contentType = MediaTypeFactory.getMediaType(key).orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(contentType);
        InitiateMultipartUploadResult initiateMultipartUploadResult = ossTemplate
                .initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName, key).withObjectMetadata(objectMetadata));
        String uploadId = initiateMultipartUploadResult.getUploadId();

        SysFileUploadTask task = new SysFileUploadTask();
        int chunkNum = (int) Math.ceil(param.getFileSize() * 1.0 / param.getChunkSize());
        task.setBucketName(ossProperties.getBucketName())
                .setChunkNum(chunkNum)
                .setChunkSize(param.getChunkSize())
                .setFileSize(param.getFileSize())
                .setIdentifier(param.getIdentifier())
                .setOriginal(original)
                .setFileName(key)
                .setUploadId(uploadId);
        baseMapper.insert(task);
        return new TaskInfoDTO().setFinished(false)
                .setTaskRecord(TaskRecordDTO.convertFromEntity(task))
                .setPath(getPath(bucketName, key));
    }

    @Override
    public String genPreSignUploadUrl(String bucket, String objectKey, Map<String, String> params) {
        Date currentDate = new Date();
        Date expireDate = DateUtil.offsetMillisecond(currentDate, ossProperties.getPreSignUrlExpire());
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, objectKey)
                .withExpiration(expireDate).withMethod(HttpMethod.PUT);
        if (params != null) {
            params.forEach((key, val) -> request.addRequestParameter(key, val));
        }
        URL preSignedUrl = ossTemplate.generatePresignedUrl(request);
        //通过网关内网反向代理到 minio server
        return preSignedUrl.toString();
    }

    @Override
    public void merge(String identifier) {
        SysFileUploadTask task = getByIdentifier(identifier);
        if (task == null) {
            throw new RuntimeException("分片任务不存");
        }

        ListPartsRequest listPartsRequest = new ListPartsRequest(task.getBucketName(), task.getFileName(), task.getUploadId());
        PartListing partListing = ossTemplate.listParts(listPartsRequest);
        List<PartSummary> parts = partListing.getParts();
        if (!task.getChunkNum().equals(parts.size())) {
            // 已上传分块数量与记录中的数量不对应，不能合并分块
            throw new RuntimeException("分片缺失，请重新上传");
        }
        CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest()
                .withUploadId(task.getUploadId())
                .withKey(task.getFileName())
                .withBucketName(task.getBucketName())
                .withPartETags(parts.stream().map(partSummary ->
                        new PartETag(partSummary.getPartNumber(), partSummary.getETag()))
                        .collect(Collectors.toList()));
        ossTemplate.completeMultipartUpload(completeMultipartUploadRequest);
        saveFileLog(task);
    }

    private void saveFileLog(SysFileUploadTask task) {
        SysFile sysFile = new SysFile();
        sysFile.setFileName(task.getFileName());
        sysFile.setOriginal(task.getOriginal());
        sysFile.setFileSize(task.getFileSize());
        sysFile.setType(FileUtil.extName(task.getOriginal()));
        sysFile.setBucketName(ossProperties.getBucketName());
        sysFile.setCreateBy(SecurityUtils.getUser().getUsername());
        sysFile.setUpdateBy(SecurityUtils.getUser().getUsername());
        sysFile.setCreateTime(LocalDateTime.now());
        sysFile.setUpdateTime(LocalDateTime.now());
        sysFileService.save(sysFile);
    }
}
